Head First设计模式阅读笔记
Created at 2020-02-19 Updated at 2020-07-29 Category 设计模式 views
设计原则
- 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。
- 针对接口编程,而不是针对实现编程。
- 多用组合,少用继承
- 为了交互对象之间的松耦合设计而努力
- 类应该对扩展开放,对修改关闭
- 依赖倒置原则:要依赖抽象,不要依赖具体类
策略模式
定义:策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
例子:有一个鸭子模拟游戏,要让鸭子有一个起飞的功能,假如在 Duck 父类上面加一个 fly() 方法,所有鸭子都会继承 fly() 方法,但是橡皮鸭不会飞,所以要把飞行行为从 Duck 类中抽取出来,分开变化和不会变化的部分。
Duck 类
public abstract class Duck {
// 为行为接口类型声明两个引用变量,所有鸭子子类都继承它们
FlyBehavior FlyBehavior;
QuackBehavior QuackBehavior;
public Duck() {
}
// 委托给行为类
public void performFly() {
FlyBehavior.fly();
}
public void performQuack() {
QuackBehavior.quack();
}
public void swim
{
System.out.println("All ducks float, even decoys");
}
}
FlyBehavior 接口
//所有飞行行为类必须实现的接口
public interface FlyBehavior {
public void fly();
}
// 飞行行为实现,给真的会飞的鸭子用
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}
// 给不会飞的鸭子用(橡皮鸭)
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.printn("I can't fly");
}
}
QuackBehavior 类
public interface QuackBehavior {
public void quack();
}
// 呱呱叫
public class Quack implements QuackBehavior {
public void quack() {
System.out.println("Quack");
}
}
// 不会叫
public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.printn("<< Silence >>");
}
}
// 吱吱叫
public class Squeak implements QuackBehavior {
public void quack() {
System.out.printn("squeak");
}
}
ModelDuck 类
public class ModelDuck extends Duck {
public ModelDuck() {
// 设置 flyBehavior 和 quackBehavior 的实例变量
quackBehavior = new Quack();
flyBehavior = new FlyNoWay();
}
}
测试类
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck model = new ModelDuck();
model.performQuack();
model.performFly();
}
}
动态设定行为
可以在鸭子子类中通过“设定方法”来设定鸭子的行为,为u不是在鸭子构造期内实例化。
- 在 Duck 类中,加入新方法
public void setFlyBehavior(flyBehavior fb){
flyBehavior(fb);
}
- 改变测试类,设定行为
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck model = new ModelDuck();
model.performQuack();
model.performFly();
//使本来不会飞的模型鸭可以飞了
model.setFlyBehavior(new FlyWithWings());
model.performFly();
}
}
观察者模式
定义:观察者模式定义了对象之间一对多依赖,这样一来,当一个对象改变状态使,它的所有依赖着都会收到通知并更新。
例子:从气象站拿到三个测量值,温度、湿度与气压,当有新的测量数据时,三个新的布告板要更新,分别是“目前状况”、“气象统计”、“天气预报”。
public interface Subject {
// 这两个方法都需要一个观察者作为变量,该观察者是用来注册或被删除的
public void registerObserver(Observer o);
public void removeObserver(Observer o);
//当主题状态改变时,这个方法会被调用,以通知所有的观察者
public void notifyObservers();
}
public interface Observer {
//所有观察者都必须实现 update() 方法,以实现观察者接口
public void update(float temp, float humidity, float pressure);
}
public interface DisplayElement {
//当布告板需要显示时,调用此方法
public void display();
}
实现观察者接口
public class WeatherData implements Subject {
//使用一个 ArrayList 来记录观察者
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList();
}
//注册观察者
public void registerObserver(Observer o) {
observers.add(o);
}
//取消注册
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}
public void notifyObservers() {
for (Observer o : observers) {
o.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
目前状况布告板
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
//构造器需要 weatherData 对象作为注册之用
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
//当 update 被调用时,我们把温度和湿度保存起来,然后调用 display()
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
}
}
推与拉模式
上面的代码是推模式的实现,推模式的主题将所有数据推给观察者,有时候观察者并不需要所有数据,如果采用拉模式,就是由观察者自己去取得所需要的数据,而且这样在扩展功能时主题就不用修改和更新对每位观察者的调用,只需改变自己来允许更多的 getter 方法来取得新增的状态。
使用 Java 内置的观察者模式
java.util 包内包含 Observer 接口与 Observerable 类,同时支持“推”和“拉”。
可观察者如何发送通知:
- 先调用 setChanged() 来标记状态已经改变。
- 然后调用
notifyObservers()(拉模式)或notifyObservers(Object arg)(推模式,当通知时,可以传送任何数据对象给每一个观察者)。
setChanged() 方法可以让你在更新观察者时有更多的弹性,可以更适当的通知观察者,比如气象站测量的数据每十分之一度就会更新,如果我们希望半度以上才更新,可以在温度差距达到半度时,调用 setChanged(),进行有效的更新。
观察者如何接收通知:
- 调用
update(Observable o, Object arg),第一个变量时是可观察者,让观察者知道是哪个主题通知它的,第二个变量正是传入notifyObservers()的数据对象,如果没有说明则为空。
利用内助的观察者模式重做
import java.util.Observable;
import java.util.Observer;
//继承 Observable,注册与删除在超类中。
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
}
public void measurementsChanged() {
//表示状态以改变
setChanged();
//notifyObservers() 中没有传送数据对象,表示采用拉模型
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
//由于我们采用拉模型,所以观察者会利用这些方法取得 WeatherData 对象的状态
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
//目前状况布告板
public class CurrentConditionsDisplay implements Observer, DisplayElement {
Observable observable;
private float temperature;
private float humidity;
//注册观察者
public CurrentConditionsDisplay(Observable obs) {
this.observable = obs;
obs.addObserver(this);
}
//先确定可观察者属于 WeatherData 类,然后利用 getter 方法获取温度和湿度
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData) obs;
this.temperature = temperature;
this.humidity = humidity;
display();
}
}
public void display() {
System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
}
}
java.util.Observable 的黑暗面
- Observable 是一个类而不是接口,所以必须设计一个类来继承它,如果某类相同时具有 Observable 和另一个超类的行为,就会陷入两难,毕竟 Java 不支持多重继承。这限制了 Observable 的复用潜力。
- 因为没有 Observable 接口,就无法建立自己的实现,也无法将它的实现换成另一套做法的实现。
装饰者模式
定义:装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
- 装饰者和被装饰者有相同的超类型
- 可以用一个或者多个装饰者包装一个对象
- 既然装饰者和被装饰者有相同的超类型,所以在任何需要原始对象的场合,可以用装饰过的对象代替它
- 装饰者可以在所委托被装饰者的行为之前/或之后,加上自己的行为,以达到特定的目的
例子:饮料需要加入各种不同的调料,还要根据加入的调料收取不同的费用。如果利用继承,就会出现类数量爆炸、设计死板,以及基类加入的新功能并不适用于所有的子类的问题。所以,这里要采用不一样的做法:我们要以饮料为主体,然后在运行时加以调料来“装饰”饮料,比如顾客要摩卡和奶泡深焙咖啡。
- 拿一个深焙咖啡对象
- 以摩卡对象装饰它
- 以奶泡对象装饰它
- 调用 cost() 方法,并依赖委托将调料的价钱加上去
饮料类
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
调料类
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso"
}
public double cost() {
return 1.99;
}
}
摩卡和奶泡类
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
public double cost() {
return 0.20 + beverage.cost();
}
}
public class Whip extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
public double cost() {
return 0.30 + beverage.cost();
}
}
测试类
public class Coffee {
public static void main(String args[]) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
beverage = new Mocha(beverage);
beverage = new Whip(beverage);
System.out.println(beverage.getDescription() + " $" + beverage.cost());
}
}
Java I/O 流使用了装饰者模式。
工厂模式
假如你有一个披萨店,有很多类型的披萨。
Pizza OrderPizza(String type) {
Pizza pizza;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
如果要增加或者修改某些类型的披萨,就要修改上面的代码,这个代码没有对修改封闭,这些是会变化的部分。
简单工厂
封装创建对象的代码,将创建披萨的代码移到另一个对象中,由这个新对象专职创建披萨。我们称这个新对象为“工厂”。
public class SimplePizzaFactory {
public Pizza CreatePizza(String type) {
Pizza pizza;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
return pizza;
}
}
这么做有什么好处?
- SimplePizzaFactory 可以有许多的客户,虽然现在只看到 orderPizze() 方法是它的客户,然而可能还有菜单类会利用这个工厂来取得披萨的价钱和描述,还可能有一个宅急送类。
简单工厂其实不是一个设计模式,反而比较像是一种编程习惯。
工厂方法
定义:工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
通过子类来创建对象,客户只需要他们所使用的抽象类型就可以了,而由字类负责决定具体类型,负责将客户从具体类型解耦。
使用工厂方法改写上面的例子(与书中不同)
public abstract class PizzaStore {
public Pizza orderPizza() {
Pizza pizza;
pizza = createPizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
//工厂方法是抽象的,所以依赖子类来处理对象的创建
protected abstract Pizza createPizza();
}
生产 CheesePizza 的工厂
public class CheeseFactory extends PizzaStore {
public Pizza createPizza() {
return new CheesePizza();
}
}
生产 GreekPizza 的工厂
public class GreekFactory extends PizzaStore {
public Pizza createPizza() {
return new GreekPizza();
}
}
Pizza 类省略
测试类
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore cheeseFactory = new CheeseFactory();
PizzaStore greekFactory = new GreekFactory();
Pizza pizza = cheeseFactory.orderPizza();
System.out.println("order a " + pizza.getName() + "\n");
pizza = greekFactory.orderPizza();
System.out.println("order a " + pizza.getName() + "\n");
}
}
抽象工厂
定义:抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
提供一个用来创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法。要想使用这个工厂,必须先实例化它,然后将它传入一些针对抽象类型所写的代码中。可以把客户从所使用的实际具体产品中解耦。
可以把一群相关的产品集合起来。
例子:不同地区的加盟店会用到不同的原料,我们需要建造一个工厂来生产原料。
先为工厂定义一个接口,负责创建所有原料
public interface PizzaIngredientFactory {
//所有原料都是一个类
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
}
创建纽约原料工厂
public class NYIngredientFactory implements PizzaIngredientFactory {
//对于每一种原料,都提供了纽约版本
public Dough createDough() {
return new ThinCrustDough();
}
public Sauce createSauce() {
return new MarinaraSauce();
}
public Cheese createCheese() {
return new ReggianoCheese();
}
}
披萨类
public abstract class Pizza {
Stirng name;
Dough dough;
Sauce sauce;
Cheese cheese;
abstract void prepare();
void bake() {
//省略
}
void cut() {
}
void box() {
}
}
CheesePizza 类
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
void prepare() {
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
//ClamPizza 省略
纽约加盟店
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String item) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYIngredientFactory();
if (item.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
} else if (item.equals("clam")) {
pizza = new ClamPizza(ingredientFactory);
}
return pizza;
}
}
工厂方法潜伏在抽象工厂里。原料类中有工厂方法。
单例模式
定义:单例模式确保一个类只有一个实例,并提供一个全局访问点。
有一些对象其实我们只需要一个,比方说:线程池、缓存。这些类对象只能有一个实例。单例模式常常被用来管理共享资源,例如数据库连接或者线程池。
单例模式的实现见单例模式 JAVA
命令模式
定义:命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志类参数化其他对象。命令模式也支持可撤销的操作。